1、APB桥
APB桥是AMBA APB总线上的唯一主机,也是AMBA AHB的一个从机。下图表示了APB桥接口信号:
APB Bridge将AHB传输转成APB传输并实现一下功能:
(1)对锁存的地址进行译码并产生选择信号PSELx,在传输过程中只有一个选择信号可以被激活。 也就是选择出唯一 一个APB从设备以进行读写动作。
(2)写操作时:负责将AHB送来的数据送上APB总线。
(3)读操作时:负责将APB的数据送上AHB系统总线。
(4)产生一时序选通信号PENABLE来作为数据传递时的启动信号
2、读传输
下图表示了APB到AHB的读传输:
传输开始于AHB总线上的T1时刻,在T2时刻地址信息被APB采样,如果传输目标是外设总线,那么这个地址就会被译码成选择信号发给外设,T2即为APB总线的SETUP周期,T3为APB的ENABLE周期,PENABLE信号拉高。在该周期内,外设必须提供读数据,通常情况下,读数据直接AHB读数据总线上,总线主机在T4时刻对读数据进行采样。
在频率很高的情况下,在ENABLE CYCLE中可能数据不能够直接映射到AHB总线,需要在APB桥中在T4的时候打插入一个额外的等待周期,并在T5的时候才被AHB主采样。虽然需要多一个等待周期(一共2个),但是由于频率提升了因此总的性能也提升了。
下图表示了一个读突发传输,所有的传输都只有一个等待周期
3、写传输
下图表示了一个写传输:
APB总线上的单块数据写操作不需要等待周期。 APB桥负责对地址和数据进行采样,并在写操作的过程中保持它们的值。
下图表示了一个写突发传输:
虽然第一个传输可以零等待完成,但后续每一个传输都需要插入一个等待状态。
APB桥需要两个地址寄存器,以便在当前传输进行时,锁存下一次传输的地址。
4、背靠背传输
下图表示了一个背靠背传输,顺序为写、读、写、读:
如果写操作之后跟随着读操作,那么需要 3 个等待周期来完成这个读操作。通常的情况下,不会有读操作之后紧跟着写操作的发生,因为两者之间 CPU 会进行指令读取,并且指令存储器也不太可能挂在在APB总线上。
下面以ARM DesignStart项目提供的软件包里的AHB转APB桥的代码,对其进行学习与仿真,以深入理解APB桥的实现方法,该转换桥比较简单,实现的是一对一的转换,也可以配合APB slave multiplexer模块,实现一对多的方式(主要依靠APB高位地址译码得到各个从机的PSEL信号)。如果想学习APB系统总线,可以参考Synopsys公司的DW_APB IP,该IP最多可支持16个APB从机,并支持所有的突发传输类型。
5、AHB_to_APB Bridge的Verilog实现
`timescale 1ns / 1ps
module ahb_to_apb #(
// Parameter to define address width
// 16 = 2^16 = 64KB APB address space
parameter ADDRWIDTH = 16,
parameter REGISTER_RDATA = 1,
parameter REGISTER_WDATA = 0
)
(
//----------------------------------
// IO Declarations
//----------------------------------
input wire HCLK, // Clock
input wire HRESETn, // Reset
input wire PCLKEN, // APB clock enable signal
input wire HSEL, // Device select
input wire [ADDRWIDTH-1:0] HADDR, // Address
input wire [1:0] HTRANS, // Transfer control
input wire [2:0] HSIZE, // Transfer size
input wire [3:0] HPROT, // Protection control
input wire HWRITE, // Write control
input wire HREADY, // Transfer phase done
input wire [31:0] HWDATA, // Write data
output reg HREADYOUT, // Device ready
output wire [31:0] HRDATA, // Read data output
output wire HRESP, // Device response
// APB Output
output wire [ADDRWIDTH-1:0] PADDR, // APB Address
output wire PENABLE, // APB Enable
output wire PWRITE, // APB Write
output wire [3:0] PSTRB, // APB Byte Strobe
output wire [2:0] PPROT, // APB Prot
output wire [31:0] PWDATA, // APB write data
output wire PSEL, // APB Select
output wire APBACTIVE, // APB bus is active, for clock gating of APB bus
// APB Input
input wire [31:0] PRDATA, // Read data for each APB slave
input wire PREADY, // Ready for each APB slave
input wire PSLVERR // Error state for each APB slave
);
//----------------------------------
// Variable Declarations
//----------------------------------
reg [ADDRWIDTH-3:0] addr_reg; // Address sample register
reg wr_reg; // Write control sample register
reg [2:0] state_reg; // State for finite state machine
reg [3:0] pstrb_reg; // Byte lane strobe register
wire [3:0] pstrb_nxt; // Byte lane strobe next state
reg [1:0] pprot_reg; // PPROT register
wire [1:0] pprot_nxt; // PPROT register next state
wire apb_select; // APB bridge is selected
wire apb_tran_end; // Transfer is completed on APB
reg [2:0] next_state; // Next state for finite state machine
reg [31:0] rwdata_reg; // Read/Write data sample register
wire reg_rdata_cfg; // REGISTER_RDATA paramater
wire reg_wdata_cfg; // REGISTER_WDATA paramater
reg sample_wdata_reg; // Control signal to sample HWDATA
//----------------------------------
// Local Parameter Declarations
//----------------------------------
// State machine
localparam ST_BITS = 3;
localparam [ST_BITS-1:0] ST_IDLE = 3'b000; // Idle waiting for transaction
localparam [ST_BITS-1:0] ST_APB_WAIT = 3'b001; // Wait APB transfer
localparam [ST_BITS-1:0] ST_APB_TRNF = 3'b010; // Start APB transfer
localparam [ST_BITS-1:0] ST_APB_TRNF2 = 3'b011; // Second APB transfer cycle
localparam [ST_BITS-1:0] ST_APB_ENDOK = 3'b100; // Ending cycle for OKAY
localparam [ST_BITS-1:0] ST_APB_ERR1 = 3'b101; // First cycle for Error response
localparam [ST_BITS-1:0] ST_APB_ERR2 = 3'b110; // Second cycle for Error response
localparam [ST_BITS-1:0] ST_ILLEGAL = 3'b111; // Illegal state
//----------------------------------
// Start of Main Code
//----------------------------------
// Configuration signal
assign reg_rdata_cfg = (REGISTER_RDATA == 0) ? 1'b0 : 1'b1;
assign reg_wdata_cfg = (REGISTER_WDATA == 0) ? 1'b0 : 1'b1;
// Generate APB bridge select
assign apb_select = HSEL & HTRANS[1] & HREADY;
// Generate APB transfer ended
assign apb_tran_end = (state_reg == 3'b011) & PREADY;
assign pprot_nxt[0] = HPROT[1]; // (0) Normal, (1) Privileged
assign pprot_nxt[1] = ~HPROT[0]; // (0) Data, (1) Instruction
// Byte strobe generation
// - Only enable for write operations
// - For word write transfers (HSIZE[1]=1), all byte strobes are 1
// - For hword write transfers (HSIZE[0]=1), check HADDR[1]
// - For byte write transfers, check HADDR[1:0]
assign pstrb_nxt[0] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b00));
assign pstrb_nxt[1] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b01));
assign pstrb_nxt[2] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b10));
assign pstrb_nxt[3] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b11));
// Sample control signals
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn) begin
addr_reg <= {(ADDRWIDTH-2){1'b0}};
wr_reg <= 1'b0;
pprot_reg <= {2{1'b0}};
pstrb_reg <= {4{1'b0}};
end
else if (apb_select) begin // Capture transfer information at the end of AHB address phase
addr_reg <= HADDR[ADDRWIDTH-1:2];
wr_reg <= HWRITE;
pprot_reg <= pprot_nxt;
pstrb_reg <= pstrb_nxt;
end
end
// Sample write data control signal
// Assert after write address phase, deassert after PCLKEN=1
wire sample_wdata_set = apb_select & HWRITE & reg_wdata_cfg;
wire sample_wdata_clr = sample_wdata_reg & PCLKEN;
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
sample_wdata_reg <= 1'b0;
else if (sample_wdata_set | sample_wdata_clr)
sample_wdata_reg <= sample_wdata_set;
end
// Generate next state for FSM
// Note : case 3'b111 is not used. The design has been checked that
// this illegal state cannot be entered using formal verification.
always @(state_reg or PREADY or PSLVERR or apb_select or reg_rdata_cfg or
PCLKEN or reg_wdata_cfg or HWRITE)
begin
case (state_reg)
// Idle
ST_IDLE : begin
if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
next_state = ST_APB_TRNF; // Start APB transfer in next cycle
else if (apb_select)
next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
else
next_state = ST_IDLE; // Remain idle
end
// Transfer announced on AHB, but PCLKEN was low, so waiting
ST_APB_WAIT : begin
if (PCLKEN)
next_state = ST_APB_TRNF; // Start APB transfer in next cycle
else
next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
end
// First APB transfer cycle
ST_APB_TRNF : begin
if (PCLKEN)
next_state = ST_APB_TRNF2; // Change to second cycle of APB transfer
else
next_state = ST_APB_TRNF; // Change to state-2
end
// Second APB transfer cycle
ST_APB_TRNF2 : begin
if (PREADY & PSLVERR & PCLKEN) // Error received - Generate two cycle
// Error response on AHB by
next_state = ST_APB_ERR1; // Changing to state-5 and 6
else if (PREADY & (~PSLVERR) & PCLKEN) begin // Okay received
if (reg_rdata_cfg)
// Registered version
next_state = ST_APB_ENDOK; // Generate okay response in state 4
else
// Non-registered version
next_state = {2'b00, apb_select}; // Terminate transfer
end
else // Slave not ready
next_state = ST_APB_TRNF2; // Unchange
end
// Ending cycle for OKAY (registered response)
ST_APB_ENDOK : begin
if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
next_state = ST_APB_TRNF; // Start APB transfer in next cycle
else if (apb_select)
next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
else
next_state = ST_IDLE; // Remain idle
end
// First cycle for Error response
ST_APB_ERR1 :
next_state = ST_APB_ERR2; // Goto 2nd cycle of error response
// Second cycle for Error response
ST_APB_ERR2 : begin
if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
next_state = ST_APB_TRNF; // Start APB transfer in next cycle
else if (apb_select)
next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
else
next_state = ST_IDLE; // Remain idle
end
default : // Not used
next_state = 3'bxxx; // X-Propagation
endcase
end
// Registering state machine
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
state_reg <= 3'b000;
else
state_reg <= next_state;
end
// Sample PRDATA or HWDATA
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
rwdata_reg <= {32{1'b0}};
else
if (sample_wdata_reg & reg_wdata_cfg & PCLKEN)
rwdata_reg <= HWDATA;
else if (apb_tran_end & reg_rdata_cfg & PCLKEN)
rwdata_reg <= PRDATA;
end
// Connect outputs to top level
assign PADDR = {addr_reg, 2'b00}; // from sample register
assign PWRITE = wr_reg; // from sample register
// From sample register or from HWDATA directly
assign PWDATA = (reg_wdata_cfg) ? rwdata_reg : HWDATA;
assign PSEL = (state_reg == ST_APB_TRNF) | (state_reg == ST_APB_TRNF2);
assign PENABLE = (state_reg == ST_APB_TRNF2);
assign PPROT = {pprot_reg[1], 1'b0, pprot_reg[0]};
assign PSTRB = pstrb_reg[3:0];
// Generate HREADYOUT
always @(state_reg or reg_rdata_cfg or PREADY or PSLVERR or PCLKEN)
begin
case (state_reg)
ST_IDLE : HREADYOUT = 1'b1; // Idle
ST_APB_WAIT : HREADYOUT = 1'b0; // Transfer announced on AHB, but PCLKEN was low, so waiting
ST_APB_TRNF : HREADYOUT = 1'b0; // First APB transfer cycle
// Second APB transfer cycle:
// if Non-registered feedback version, and APB transfer completed without error
// Then response with ready immediately. If registered feedback version,
// wait until state_reg == ST_APB_ENDOK
ST_APB_TRNF2 : HREADYOUT = (~reg_rdata_cfg) & PREADY & (~PSLVERR) & PCLKEN;
ST_APB_ENDOK : HREADYOUT = reg_rdata_cfg; // Ending cycle for OKAY (registered response only)
ST_APB_ERR1 : HREADYOUT = 1'b0; // First cycle for Error response
ST_APB_ERR2 : HREADYOUT = 1'b1; // Second cycle for Error response
default : HREADYOUT = 1'bx; // x propagation (note :3'b111 is illegal state)
endcase
end
// From sample register or from PRDATA directly
assign HRDATA = (reg_rdata_cfg) ? rwdata_reg : PRDATA;
assign HRESP = (state_reg == ST_APB_ERR1) | (state_reg == ST_APB_ERR2);
assign APBACTIVE = (HSEL & HTRANS[1]) | (|state_reg);
endmodule
使用的测试用例和AHB从机的测试用例基本一样,首先是顶层:
`timescale 1ns / 1ps
module top_tb();
//----------------------------------
// Local Parameter Declarations
//----------------------------------
localparam AHB_CLK_PERIOD = 5; // Assuming AHB CLK to be 100MHz
localparam SIZE_IN_BYTES = 2048;
localparam ADDRWIDTH = 32;
//----------------------------------
// Variable Declarations
//----------------------------------
reg HCLK = 0;
wire HWRITE;
wire [1:0] HTRANS;
wire [2:0] HSIZE;
wire [2:0] HBURST;
wire HREADYIN;
wire [31:0] HADDR;
wire [3:0] HPROT;
wire [31:0] HWDATA;
wire HREADYOUT;
wire [1:0] HRESP;
wire [31:0] HRDATA;
reg HRESETn;
wire HREADY;
wire [ADDRWIDTH-1:0] PADDR; // APB Address
wire PENABLE; // APB Enable
wire PWRITE; // APB Write
wire [31:0] PWDATA; // APB write data
wire PSEL; // APB Select
wire PREADY;
wire PSLVERR;
wire [2:0] PPROT;
wire [3:0] PSTRB;
wire [31:0] PRDATA;
//----------------------------------
// Start of Main Code
//----------------------------------
assign HREADY = HREADYOUT;
//-----------------------------------------------------------------------
// Generate HCLK
//-----------------------------------------------------------------------
always #AHB_CLK_PERIOD
HCLK <= ~HCLK;
//-----------------------------------------------------------------------
// Generate HRESETn
//-----------------------------------------------------------------------
initial begin
HRESETn = 1'b0;
repeat(5) @(posedge HCLK);
HRESETn = 1'b1;
end
ahb_master #(
.START_ADDR (32'h0),
.DEPTH_IN_BYTES (SIZE_IN_BYTES)
)
u_ahb_master (
.HRESETn (HRESETn),
.HCLK (HCLK),
.HADDR (HADDR),
.HPROT (HPROT),
.HTRANS (HTRANS),
.HWRITE (HWRITE),
.HSIZE (HSIZE),
.HBURST (HBURST),
.HWDATA (HWDATA),
.HRDATA (HRDATA),
.HRESP (HRESP),
.HREADY (HREADYOUT)
);
ahb_to_apb #(
.ADDRWIDTH (ADDRWIDTH),
.REGISTER_RDATA (0),
.REGISTER_WDATA (0)
)
u_ahb_to_apb(
.HCLK (HCLK),
.HRESETn (HRESETn),
.PCLKEN (1'b1),
.HSEL (1'b1),
.HADDR (HADDR),
.HTRANS (HTRANS),
.HSIZE (HSIZE),
.HPROT (HPROT),
.HWRITE (HWRITE),
.HREADY (HREADY),
.HWDATA (HWDATA),
.HREADYOUT (HREADYOUT),
.HRDATA (HRDATA),
.HRESP (HRESP),
.PADDR (PADDR),
.PENABLE (PENABLE),
.PWRITE (PWRITE),
.PREADY (PREADY),
.PSLVERR (PSLVERR),
.PSTRB (PSTRB),
.PPROT (PPROT),
.PWDATA (PWDATA),
.PSEL (PSEL),
.APBACTIVE (APBACTIVE),
.PRDATA (PRDATA)
);
apb_mem #(
.P_SLV_ID (0),
.ADDRWIDTH (ADDRWIDTH),
.P_SIZE_IN_BYTES (SIZE_IN_BYTES),
.P_DELAY (0)
)
u_apb_mem (
`ifdef AMBA_APB3
.PREADY (PREADY),
.PSLVERR (PSLVERR),
`endif
`ifdef AMBA_APB4
.PSTRB (PSTRB),
.PPROT (PPROT),
`endif
.PRESETn (HRESETn),
.PCLK (HCLK),
.PSEL (PSEL),
.PENABLE (PENABLE),
.PADDR (PADDR),
.PWRITE (PWRITE),
.PRDATA (PRDATA),
.PWDATA (PWDATA)
);
`ifdef VCS
initial begin
$fsdbDumpfile("top_tb.fsdb");
$fsdbDumpvars;
end
initial begin
`ifdef DUMP_VPD
$vcdpluson();
`endif
end
`endif
endmodule
然后是AHB master的功能模型:
`timescale 1ns / 1ps
`define SINGLE_TEST
`define BURST_TEST
module ahb_master #(
//----------------------------------
// Paramter Declarations
//----------------------------------
parameter START_ADDR = 0,
parameter DEPTH_IN_BYTES = 32'h100,
parameter END_ADDR = START_ADDR+DEPTH_IN_BYTES-1
)
(
//----------------------------------
// IO Declarations
//----------------------------------
input wire HRESETn,
input wire HCLK,
output reg [31:0] HADDR,
output reg [1:0] HTRANS,
output reg HWRITE,
output reg [2:0] HSIZE,
output reg [2:0] HBURST,
output reg [3:0] HPROT,
output reg [31:0] HWDATA,
input wire [31:0] HRDATA,
input wire [1:0] HRESP,
input wire HREADY
);
//----------------------------------
// Variable Declarations
//----------------------------------
reg [31:0] data_burst[0:1023];
//----------------------------------
// Start of Main Code
//----------------------------------
initial begin
HADDR = 0;
HTRANS = 0;
HPROT = 0;
HWRITE = 0;
HSIZE = 0;
HBURST = 0;
HWDATA = 0;
while(HRESETn === 1'bx) @(posedge HCLK);
while(HRESETn === 1'b1) @(posedge HCLK);
while(HRESETn === 1'b0) @(posedge HCLK);
`ifdef SINGLE_TEST
repeat(3) @(posedge HCLK);
memory_test(START_ADDR, END_ADDR, 1);
memory_test(START_ADDR, END_ADDR, 2);
memory_test(START_ADDR, END_ADDR, 4);
`endif
`ifdef BURST_TEST
repeat(5) @(posedge HCLK);
memory_test_burst(START_ADDR, END_ADDR, 1);
memory_test_burst(START_ADDR, END_ADDR, 2);
memory_test_burst(START_ADDR, END_ADDR, 4);
memory_test_burst(START_ADDR, END_ADDR, 6);
memory_test_burst(START_ADDR, END_ADDR, 8);
memory_test_burst(START_ADDR, END_ADDR, 10);
memory_test_burst(START_ADDR, END_ADDR, 16);
memory_test_burst(START_ADDR, END_ADDR, 32);
memory_test_burst(START_ADDR, END_ADDR, 64);
memory_test_burst(START_ADDR, END_ADDR, 128);
memory_test_burst(START_ADDR, END_ADDR, 255);
repeat(5) @(posedge HCLK);
`endif
$finish(2);
end
//-----------------------------------------------------------------------
// Single transfer test
//-----------------------------------------------------------------------
task memory_test;
input [31:0] start; // start address
input [31:0] finish; // end address
input [2:0] size; // data size: 1, 2, 4
integer i;
integer error;
reg [31:0] data;
reg [31:0] gen;
reg [31:0] got;
reg [31:0] reposit[START_ADDR:END_ADDR];
begin
$display("%m: read-after-write test with %d-byte access", size);
error = 0;
gen = $random(7);
for (i = start; i < (finish-size+1); i = i + size) begin
gen = $random & ~32'b0;
data = align(i, gen, size);
ahb_write(i, size, data);
ahb_read(i, size, got);
got = align(i, got, size);
if (got !== data) begin
$display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data);
error = error + 1;
end
end
if (error == 0)
$display("[%10d] %m OK: from %x to %x", $time, start, finish);
$display("%m read-all-after-write-all with %d-byte access", size);
error = 0;
gen = $random(1);
for (i = start; i < (finish-size+1); i = i + size) begin
gen = {$random} & ~32'b0;
data = align(i, gen, size);
reposit[i] = data;
ahb_write(i, size, data);
end
for (i = start; i < (finish-size+1); i = i + size) begin
data = reposit[i];
ahb_read(i, size, got);
got = align(i, got, size);
if (got !== data) begin
$display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data);
error = error + 1;
end
end
if (error == 0)
$display("[%10d] %m OK: from %x to %x", $time, start, finish);
end
endtask
//-----------------------------------------------------------------------
// Burst transfer test
//-----------------------------------------------------------------------
task memory_test_burst;
input [31:0] start; // start address
input [31:0] finish; // end address
input [7:0] leng; // burst length
integer i;
integer j;
integer k;
integer r;
integer error;
reg [31:0] data;
reg [31:0] gen;
reg [31:0] got;
reg [31:0] reposit[0:1023];
integer seed;
begin
$display("[%10d] %m: read-all-after-write-all burst test with %d-beat access", $time, leng);
error = 0;
seed = 111;
gen = $random(seed);
k = 0;
if (finish > (start+leng*4)) begin
for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin
for (j = 0; j < leng; j = j + 1) begin
data_burst[j] = $random;
reposit[j+k*leng] = data_burst[j];
end
@(posedge HCLK);
ahb_write_burst(i, leng);
k = k + 1;
end
gen = $random(seed);
k = 0;
for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin
@(posedge HCLK);
ahb_read_burst(i, leng);
for (j = 0; j < leng; j = j + 1) begin
if (data_burst[j] != reposit[j+k*leng]) begin
error = error+1;
$display("[%10d] %m A=%hh D=%hh, but %hh expected", $time, i+j*leng, data_burst[j], reposit[j+k*leng]);
end
end
k = k + 1;
r = $random & 8'h0F;
repeat(r) @(posedge HCLK);
end
if (error == 0)
$display("%m %d-length burst read-after-write OK: from %hh to %hh",leng, start, finish);
end
else begin
$display("%m %d-length burst read-after-write from %hh to %hh ???",leng, start, finish);
end
end
endtask
//-----------------------------------------------------------------------
// As AMBA AHB bus uses non-justified data bus scheme, data should be
// aligned according to the address.
//-----------------------------------------------------------------------
function [31:0] align;
input [ 1:0] addr;
input [31:0] data;
input [ 2:0] size; // num of bytes
begin
`ifdef BIG_ENDIAN
case (size)
1 :
case (addr[1:0])
0 : align = data & 32'hFF00_0000;
1 : align = data & 32'h00FF_0000;
2 : align = data & 32'h0000_FF00;
3 : align = data & 32'h0000_00FF;
endcase
2 :
case (addr[1])
0 : align = data & 32'hFFFF_0000;
1 : align = data & 32'h0000_FFFF;
endcase
4 :
align = data&32'hFFFF_FFFF;
default :
$display($time,,"%m ERROR %d-byte not supported for size", size);
endcase
`else
case (size)
1 :
case (addr[1:0])
0 : align = data & 32'h0000_00FF;
1 : align = data & 32'h0000_FF00;
2 : align = data & 32'h00FF_0000;
3 : align = data & 32'hFF00_0000;
endcase
2 :
case (addr[1])
0 : align = data & 32'h0000_FFFF;
1 : align = data & 32'hFFFF_0000;
endcase
4 :
align = data&32'hFFFF_FFFF;
default :
$display($time,,"%m ERROR %d-byte not supported for size", size);
endcase
`endif
end
endfunction
`include "ahb_transaction_tasks.v"
endmodule
ahb_transaction_tasks.v文件如下:
`ifndef __AHB_TRANSACTION_TASKS_V__
`define __AHB_TRANSACTION_TASKS_V__
//-----------------------------------------------------------------------
// AHB Read Task
//-----------------------------------------------------------------------
task ahb_read;
input [31:0] address;
input [2:0] size;
output [31:0] data;
begin
@(posedge HCLK);
HADDR <= #1 address;
HPROT <= #1 4'b0001; // DATA
HTRANS <= #1 2'b10; // NONSEQ;
HBURST <= #1 3'b000; // SINGLE;
HWRITE <= #1 1'b0; // READ;
case (size)
1 : HSIZE <= #1 3'b000; // BYTE;
2 : HSIZE <= #1 3'b001; // HWORD;
4 : HSIZE <= #1 3'b010; // WORD;
default :
$display($time,, "ERROR: unsupported transfer size: %d-byte", size);
endcase
@(posedge HCLK);
while (HREADY !== 1'b1) @(posedge HCLK);
HTRANS <= #1 2'b0; // IDLE
@(posedge HCLK);
while (HREADY === 0) @(posedge HCLK);
data = HRDATA; // must be blocking
if (HRESP != 2'b00)
$display($time,, "ERROR: non OK response for read");
@(posedge HCLK);
end
endtask
//-----------------------------------------------------------------------
// AHB Write Task
//-----------------------------------------------------------------------
task ahb_write;
input [31:0] address;
input [2:0] size;
input [31:0] data;
begin
@(posedge HCLK);
HADDR <= #1 address;
HPROT <= #1 4'b0001; // DATA
HTRANS <= #1 2'b10; // NONSEQ
HBURST <= #1 3'b000; // SINGLE
HWRITE <= #1 1'b1; // WRITE
case (size)
1 : HSIZE <= #1 3'b000; // BYTE
2 : HSIZE <= #1 3'b001; // HWORD
4 : HSIZE <= #1 3'b010; // WORD
default :
$display($time,, "ERROR: unsupported transfer size: %d-byte", size);
endcase
@(posedge HCLK);
while (HREADY !== 1) @(posedge HCLK);
HWDATA <= #1 data;
HTRANS <= #1 2'b0; // IDLE
@(posedge HCLK);
while (HREADY === 0) @(posedge HCLK);
if (HRESP != 2'b00)
$display($time,, "ERROR: non OK response write");
@(posedge HCLK);
end
endtask
//-----------------------------------------------------------------------
// AHB Read Burst Task
//-----------------------------------------------------------------------
task ahb_read_burst;
input [31:0] addr;
input [31:0] leng;
integer i;
integer ln;
integer k;
begin
k = 0;
@(posedge HCLK);
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR
ln = leng;
end
HWRITE <= #1 1'b0; // READ
HSIZE <= #1 3'b010; // WORD
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
while (leng > 0) begin
for (i = 0; i < ln-1; i = i + 1) begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b11; // SEQ;
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
data_burst[k%1024] <= HRDATA;
k = k + 1;
end
leng = leng - ln;
if (leng == 0) begin
HADDR <= #1 0;
HTRANS <= #1 0;
HBURST <= #1 0;
HWRITE <= #1 0;
HSIZE <= #1 0;
end
else begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR1
ln = leng;
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
data_burst[k%1024] = HRDATA; // must be blocking
k = k + 1;
end
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
data_burst[k%1024] = HRDATA; // must be blocking
end
endtask
//-----------------------------------------------------------------------
// AHB Write Burst Task
// It takes suitable burst first and then incremental.
//-----------------------------------------------------------------------
task ahb_write_burst;
input [31:0] addr;
input [31:0] leng;
integer i;
integer j;
integer ln;
begin
j = 0;
ln = 0;
@(posedge HCLK);
while (leng > 0) begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR
ln = leng;
end
HWRITE <= #1 1'b1; // WRITE
HSIZE <= #1 3'b010; // WORD
for (i = 0; i < ln-1; i = i + 1) begin
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
HWDATA <= #1 data_burst[(j+i)%1024];
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b11; // SEQ;
while (HREADY == 1'b0) @(posedge HCLK);
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
HWDATA <= #1 data_burst[(j+i)%1024];
if (ln == leng) begin
HADDR <= #1 0;
HTRANS <= #1 0;
HBURST <= #1 0;
HWRITE <= #1 0;
HSIZE <= #1 0;
end
leng = leng - ln;
j = j + ln;
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
if (HRESP != 2'b00) begin // OKAY
$display($time,, "ERROR: non OK response write");
end
`ifdef DEBUG
$display($time,, "INFO: write(%x, %d, %x)", addr, size, data);
`endif
HWDATA <= #1 0;
@(posedge HCLK);
end
endtask
`endif
还需要一个APB的从机模块:
`timescale 1ns / 1ps
`define AMBA_APB3
`define AMBA_APB4
module apb_mem #(
parameter P_SLV_ID = 0,
parameter ADDRWIDTH = 32,
parameter P_SIZE_IN_BYTES = 1024, // memory depth
parameter P_DELAY = 0 // reponse delay
)
(
//----------------------------------
// IO Declarations
//----------------------------------
`ifdef AMBA_APB3
output wire PREADY,
output wire PSLVERR,
`endif
`ifdef AMBA_APB4
input wire [2:0] PPROT,
input wire [3:0] PSTRB,
`endif
input wire PRESETn,
input wire PCLK,
input wire PSEL,
input wire PENABLE,
input wire [ADDRWIDTH-1:0] PADDR,
input wire PWRITE,
output reg [31:0] PRDATA = 32'h0,
input wire [31:0] PWDATA
);
//----------------------------------
// Local Parameter Declarations
//----------------------------------
localparam DEPTH = (P_SIZE_IN_BYTES+3)/4;
localparam AW = logb2(P_SIZE_IN_BYTES);
//----------------------------------
// Variable Declarations
//----------------------------------
`ifndef AMBA_APB3
wire PREADY;
`else
assign PSLVERR = 1'b0;
`endif
`ifndef AMBA_APB4
wire [3:0] PSTRB = 4'hF;
`endif
reg [7:0] mem0[0:DEPTH-1];
reg [7:0] mem1[0:DEPTH-1];
reg [7:0] mem2[0:DEPTH-1];
reg [7:0] mem3[0:DEPTH-1];
wire [AW-3:0] TA = PADDR[AW-1:2];
//----------------------------------
// Start of Main Code
//----------------------------------
//--------------------------------------------------------------------------
// write transfer
// ____ ____ ____
// PCLK ___| |____| |____| |_
// ____ ___________________ _____
// PADDR ____X__A________________X_____
// ____ ___________________ _____
// PWDATA ____X__DW_______________X_____
// ___________________
// PWRITE ____| |_____
// ___________________
// PSEL ____| |_____
// _________
// PENABLE ______________| |_____
//--------------------------------------------------------------------------
always @(posedge PCLK)
begin
if (PRESETn & PSEL & PENABLE & PWRITE & PREADY) begin
if (PSTRB[0])
mem0[TA] <= PWDATA[ 7: 0];
if (PSTRB[1])
mem1[TA] <= PWDATA[15: 8];
if (PSTRB[2])
mem2[TA] <= PWDATA[23:16];
if (PSTRB[3])
mem3[TA] <= PWDATA[31:24];
end
end
//--------------------------------------------------------------------------
// read
// ____ ____ ____
// PCLK ___| |____| |____| |_
// ____ ___________________ _____
// PADDR ____X__A________________X_____
// ____ _________ _____
// PRDATA ____XXXXXXXXXXX__DR_____X_____
// ____ _____
// PWRITE ____|___________________|_____
// ___________________
// PSEL ____| |_____
// _________
// PENABLE ______________| |_____
//--------------------------------------------------------------------------
always @(posedge PCLK)
begin
if (PRESETn & PSEL & ~PENABLE & ~PWRITE) begin
PRDATA[ 7: 0] <= mem0[TA];
PRDATA[15: 8] <= mem1[TA];
PRDATA[23:16] <= mem2[TA];
PRDATA[31:24] <= mem3[TA];
end
end
`ifdef AMBA_APB3
localparam ST_IDLE = 'h0,
ST_CNT = 'h1,
ST_WAIT = 'h2;
reg [7:0] count;
reg ready;
reg [1:0] state=ST_IDLE;
assign PREADY = (P_DELAY == 0) ? 1'b1 : ready;
always @(posedge PCLK or negedge PRESETn)
begin
if (PRESETn == 1'b0) begin
count <= 'h0;
ready <= 'b1;
state <= ST_IDLE;
end else begin
case (state)
ST_IDLE : begin
if (PSEL && (P_DELAY > 0)) begin
ready <= 1'b0;
count <= 'h1;
state <= ST_CNT;
end
else begin
ready <= 1'b1;
end
end // ST_IDLE
ST_CNT : begin
count <= count + 1;
if (count >= P_DELAY) begin
count <= 'h0;
ready <= 1'b1;
state <= ST_WAIT;
end
end // ST_CNT
ST_WAIT : begin
ready <= 1'b1;
state <= ST_IDLE;
end // ST_WAIT
default : begin
ready <= 1'b1;
state <= ST_IDLE;
end
endcase
end // if
end // always
`else
assign PREADY = 1'b1;
`endif
// Calculate log-base2
function integer logb2;
input [31:0] value;
reg [31:0] tmp;
begin
tmp = value - 1;
for (logb2 = 0; tmp > 0; logb2 = logb2 + 1)
tmp = tmp >> 1;
end
endfunction
// synopsys translate_off
`ifdef RIGOR
always @(posedge PCLK or negedge PRESETn)
begin
if (PRESETn == 1'b0) begin
end
else begin
if (PSEL & PENABLE) begin
if (TA >= DEPTH)
$display($time,,"%m: ERROR: out-of-bound 0x%x", PADDR);
end
end
end
`endif
// synopsys translate_on
endmodule
6、仿真
用VCS进行仿真,打印信息如下
可见仿真完全正确,这里也只是做了AHB总线的单一传输和各种长度的增量突发,回环突发未涉及(对APB桥来说,它并不关心HBURST的信号值)。
下面挂两张仿真截图:
单一传输,先写后读:
突发传输,先写完,再读完
想问下为什么BURST读的时候,看波形数据是在HREADY为0的时候变化的?